package model;

import exception.ExtraCharactersException;
import exception.IllegalCharacterSequenceException;
import exception.LessCharactersExeption;
import exception.NoRuleException;
import guard_grammar.Evaluation;

import java.io.IOException;
import java.util.LinkedList;

/**
 * @author Darko Martinovikj
 * @version 1.2 01.03.2013
 * 
 *          Class representing a graph with variables
 * */
public class ProgramGraph
{

	private int nodesCount;
	private NodeProgramGraph adjList[];
	private int startNodeIndex;
	private TransitionHashTable transitionTable;
	private TransitionRestrictionHashTable transitionRestrictionTable;
	private VariableHashTable initialVariableValues;
	private VariableHashTable variableDomainFromTable;
	private VariableHashTable variableDomainToTable;
	private String restrictionFilePath;

	/**
	 * This method is the constructor of the class.
	 * 
	 * @param int maxNumberNodes- the maximal number of nodes in the graph
	 */
	public ProgramGraph(int maxNumberNodes)
	{
		this.nodesCount = 0;
		adjList = new NodeProgramGraph[maxNumberNodes];
		startNodeIndex = -1;
		transitionTable = new TransitionHashTable();
		transitionRestrictionTable = new TransitionRestrictionHashTable();
		variableDomainFromTable = new VariableHashTable();
		variableDomainToTable = new VariableHashTable();
	}

	/**
	 * This method is the constructor of the class. Creates a new empty graph with maximal number of
	 * 10000 nodes
	 * 
	 */
	public ProgramGraph()
	{
		this.nodesCount = 0;
		adjList = new NodeProgramGraph[10000];
		startNodeIndex = -1;
		transitionTable = new TransitionHashTable();
		transitionRestrictionTable = new TransitionRestrictionHashTable();
		variableDomainFromTable = new VariableHashTable();
		variableDomainToTable = new VariableHashTable();
	}

	/**
	 * Method for adding a Node to the graph. The node index and name must be specified. The node by
	 * default is not bad.
	 * 
	 * @param int index- the index of the node
	 * @param String
	 *            name - the name of the node
	 */
	public void addNode(int index, String name)
	{
		adjList[index] = new NodeProgramGraph(index, name);
		nodesCount++;
	}

	/**
	 * Method for adding a Node to the graph. The node index and name must be specified. Also is the
	 * node bad must be specified.
	 * 
	 * @param int index - the index of the node
	 * @param String
	 *            name - the name of the node
	 * @param boolean isBadState - is the node bad
	 */
	public void addNode(int index, String name, boolean isBadNode)
	{
		adjList[index] = new NodeProgramGraph(index, name, isBadNode);
		nodesCount++;
	}

	/**
	 * Method for adding a edge in the graph. The indexes of the two nodes must be specified and
	 * also the transition information Also the transition is added in the transition hash table as
	 * controllable transition
	 * 
	 * @param int indexFrom - the index of the from node
	 * @param String
	 *            guardString - the guard string
	 * @param String
	 *            actionString - the actions string
	 * @param String
	 *            transitionName - the name of the transition
	 * @param int indexTo - the index of the to node
	 */
	public void addEdge(int indexFrom, String guardString, String actionString,
			String transitionName, int indexTo)
	{
		adjList[indexFrom].addNeighbor(adjList[indexTo], guardString, actionString, transitionName);
		transitionTable.insertTransition(transitionName, true);
	}

	/**
	 * Method for setting the initial node to be the specified node with given index.
	 * 
	 * @param int index - the index of node we want to be initial
	 */
	public void setInitialNode(int index)
	{
		startNodeIndex = index;
	}

	/**
	 * Method for getting the initial node if exists. If that node doesn't exist than a null is
	 * returned
	 * 
	 * @return NodeProgramGraph - the initial node or null
	 */
	public NodeProgramGraph getInitialNode()
	{
		if (startNodeIndex == -1)
			return null;
		else
			return adjList[startNodeIndex];
	}

	/**
	 * Method for setting a node with given index to be a bad node
	 * 
	 * @param int index - the index of the node we want to be bad
	 */
	public void setBadNode(int index)
	{
		adjList[index].setBadNode(true);
	}

	/**
	 * Method for getting all of the neighbours of a node with given index
	 * 
	 * @param int index - the node index
	 * @return LinkedList<EdgeProgramGraph> - the list of all of the neighbours of the given node
	 */
	public LinkedList<EdgeProgramGraph> getNodeNeighbors(int index)
	{
		return adjList[index].getNeighbors();
	}

	/**
	 * Method for getting all of the parents of a node with given index
	 * 
	 * @param int index - the node index
	 * @return LinkedList<EdgeProgramGraph> - the list of all of the parents of the given node
	 */
	public LinkedList<EdgeProgramGraph> getNodeParents(int index)
	{
		return adjList[index].getParents();
	}

	/**
	 * This method is used for printing graph information
	 * 
	 * @return String - the graph information
	 */
	@Override
	public String toString()
	{
		String ret = new String();
		for (int i = 0; i < this.nodesCount; i++)
			if (adjList[i] != null)
				ret += adjList[i] + "\n";
		return ret;
	}

	/**
	 * This method is used for printing graph information with transition information
	 * 
	 * @return String - the graph information
	 */
	public String toStringTransitionPrint()
	{
		String ret = new String();
		for (int i = 0; i < this.nodesCount; i++)
			if (adjList[i] != null)
				ret += adjList[i].toStringWithNeightbours() + "\n";
		return ret;
	}

	/**
	 * Method for returning all of the transitions in the graph
	 * 
	 * @return String[] - Arrays of string, representing transitions name
	 */
	public String[] getAllTransitions()
	{
		return transitionTable.getAllTransitions();
	}

	/**
	 * Method for returning all of the transitions which are controllable as Array of string
	 * 
	 * @return String[] - Arrays of string, representing transitions name
	 */
	public String[] getAllControllableTransitions()
	{
		return transitionTable.getAllControllableTransitions();
	}

	/**
	 * Method for returning all of the transitions which are uncontrollable as Array of string
	 * 
	 * @return String[] - Arrays of string, representing transitions name
	 */
	public String[] getAllUncontrollableTransitions()
	{
		return transitionTable.getAllUncontrollableTransitions();
	}

	/**
	 * Method for setting the type of the transition. If a transition with that name doens't exist
	 * method does nothing
	 * 
	 * @param String
	 *            transitionName - The name of the transition
	 * @param boolean isControllable - Is the transition Controllable
	 */
	public void setTransitionType(String transitionName, boolean isControllable)
	{
		if (transitionTable.isControllableTransition(transitionName) != null)
			transitionTable.insertTransition(transitionName, isControllable);
	}

	/**
	 * Method for returning if a given transition is Controllable. If that transition doesn't exist
	 * returns null
	 * 
	 * @return Boolean - if the transition is Controllable, or null if that transition doesn't exist
	 */
	public Boolean isControllableTransition(String transitionName)
	{
		return transitionTable.isControllableTransition(transitionName);
	}

	/**
	 * Method for removing the edge from the graph. The appropriate neighbour and parent links are
	 * removed
	 * 
	 * @param EdgeProgramGraph
	 *            transition - The transition Edge
	 */
	public void removeTransition(EdgeProgramGraph transition)
	{
		adjList[transition.getFromNode().getIndex()].removeNeighbour(transition);
		adjList[transition.getToNode().getIndex()].removeParent(transition);
	}

	/**
	 * Method for removing a node from the graph. All the transitions (edges) are remove before
	 * removing the node
	 * 
	 * @param int index - the index of the node
	 */
	public void removeNode(int index)
	{
		LinkedList<EdgeProgramGraph> list = new LinkedList<EdgeProgramGraph>();
		for (EdgeProgramGraph edge : adjList[index].getNeighbors())
		{
			list.add(edge);
		}
		for (EdgeProgramGraph edge : list)
			this.removeTransition(edge);
		list = new LinkedList<EdgeProgramGraph>();
		for (EdgeProgramGraph edge : adjList[index].getParents())
		{
			list.add(edge);
		}
		for (EdgeProgramGraph edge : list)
			this.removeTransition(edge);
		adjList[index] = null;
	}

	/**
	 * Method for adding a restriction to a given transition. The name of the transition must be
	 * specified
	 * 
	 * @param String
	 *            transitionName - the name of the transition
	 * @param String
	 *            transtitionRestrictionString - the restriction String
	 */
	public void addTransitionRestriction(String transitionName, String transtitionRestrictionString)
	{
		transitionRestrictionTable.insertTransition(transitionName, transtitionRestrictionString);
	}

	/**
	 * Method for getting all of the restrictions for a given transition.
	 * 
	 * @param String
	 *            transitionName the name of the transition
	 * @return LinkedList<String> - the list of Strings that are restrictions to the transition
	 */
	public LinkedList<String> getTransitionRestrictions(String transitionName)
	{
		return transitionRestrictionTable.getTransitionRestrictions(transitionName);
	}

	/**
	 * Method for setting the initial variable values of the graph
	 * @param table - Hash table with the variable names and their initial values
	 */
	public void setInitialVariableValues(VariableHashTable table)
	{
		this.initialVariableValues = table.copyHashTable();
	}

	/** Method which creates a cope of the initial variable values of the graph
	 * 
	 * @return - copied hash table with the variable names and values
	 */
	public VariableHashTable getInitialVariableValues()
	{
		return initialVariableValues.copyHashTable();
	}

	/** Method for getting the index of the node for a given Name of the node
	 * @param nodeName - the name of the node
	 * @return the index of the node
	 */
	public int getNodeIndex(String nodeName)
	{
		for (int i = 0; i < nodesCount; i++)
			if (adjList[i].getName().equals(nodeName))
				return i;
		return -1;
	}

	/** Method for inputting a domain from and to for a given variable Name
	 * @param variableName - the name of the variable
	 * @param domainFrom - the domain from
	 * @param domainTo - the domain to 
	 */
	public void addVariableDomain(String variableName, int domainFrom, int domainTo)
	{
		variableDomainFromTable.insertVariable(variableName, domainFrom);
		variableDomainToTable.insertVariable(variableName, domainTo);
	}

	/** Method for checking if the variable value is in the domain
	 * @param variableName - the name of the variable
	 * @param value - the value of the variable
	 * @return - true or false 
	 */
	public boolean checkVariableInDomain(String variableName, int value)
	{
		return (value >= variableDomainFromTable.getVariableValue(variableName) && value <= variableDomainToTable
				.getVariableValue(variableName));
	}

	
	/** Method for getting the restriction file path
	 * @return the path of the restricition file
	 */
	public String getRestrictionFilePath()
	{
		return restrictionFilePath;
	}

	/** Method for setting the restriction file path for the requirements
	 * @param restrictionFilePath - the path of the restriciton file
	 */
	public void setRestrictionFilePath(String restrictionFilePath)
	{
		this.restrictionFilePath = restrictionFilePath;
	}

	
	/** Method for getting all the variables name by parsing all the guard and update strings in all the transitions
	 * @return - list with variable names
	 * @throws IOException
	 * @throws IllegalCharacterSequenceException
	 * @throws LessCharactersExeption
	 * @throws NoRuleException
	 * @throws ExtraCharactersException
	 */
	public LinkedList<String> getAllVariables() throws IOException,
			IllegalCharacterSequenceException, LessCharactersExeption, NoRuleException,
			ExtraCharactersException
	{
		LinkedList<String> variableList = new LinkedList<String>();
		for (int i = 0; i < nodesCount; i++)
			for (EdgeProgramGraph edge : adjList[i].getNeighbors())
			{
				System.out.println(edge.getGuardString());
				Evaluation.getVariablesFromGuard(edge.getGuardString(), variableList);
				Evaluation.getVariablesFromUpdate(edge.getActionString(), variableList);
			}
		return variableList;
	}

	/** Method for getting the variable domain from table
	 * @return - the domain from table for every variable
	 */
	public VariableHashTable getVariableDomainFromTable()
	{
		return variableDomainFromTable;
	}

	/** Method for setting the variable domain from table
	 * @param variableDomainFromTable - the new domain from table
	 */
	public void setVariableDomainFromTable(VariableHashTable variableDomainFromTable)
	{
		this.variableDomainFromTable = variableDomainFromTable;
	}

	/** Method for getting the variable domain to table
	 * @return - the domain to table for every variable
	 */
	public VariableHashTable getVariableDomainToTable()
	{
		return variableDomainToTable;
	}

	
	/** Method for setting the variable domain to table
	 * @param variableDomainFromTable - the new domain to table
	 */
	public void setVariableDomainToTable(VariableHashTable variableDomainToTable)
	{
		this.variableDomainToTable = variableDomainToTable;
	}

	/** Method for getting the transition hash table with all of the transitions and their types
	 * @return - hash table with the transition names and type
	 */
	public TransitionHashTable getTransitionHashTable()
	{
		return transitionTable;
	}
}
